#ifdef ShadowMap

#if defined(MultiShadow) || defined(CascadedShadow)
#define ShadowWorkWithArray
#endif

//////////////////////// VARYINGS ////////////////////////

#ifdef ShadowWorkWithArray
  //in vec4 shadowMapCoord[MultiInterObjectsShadow]; //Pas besoin sur ios (pas de modif dans le master)

  //layout(std430) readonly buffer ShadowBuffer
  //{
  //  ShadowData_t g_shadowBuffer[];
  //};

#else 
  in vec4 shadowMapCoord;
#endif



//////////////////////// SAMPLERS ////////////////////////


float GetLightNear(int iLayer)
{
#ifndef MultiShadow
  return _sbcbShadow.LightNearFarDirRadius.x;
#else
  return g_shadowBuffer[iLayer].LightNearFarDirRadius.x;
#endif
}

float GetLightFar(int iLayer)
{
#ifndef MultiShadow
  return _sbcbShadow.LightNearFarDirRadius.y;
#else
  return g_shadowBuffer[iLayer].LightNearFarDirRadius.y;
#endif
}

int cascadedOffset = 0;

vec4 GetShadowMapCoord(int iLightID, int iCascadeID, bool iIsCascaded)
{
  mat4 offsetMatrix = mat4( 0.5, 0.0, 0.0, 0.0,
                            0.0, 0.5, 0.0, 0.0,
                            0.0, 0.0, 0.5, 0.0,
                            0.5, 0.5, 0.5, 1.0 );

  mat4 projMatrix;
  vec4 coord;


  vec4 viewPos = vec4(vGetViewPosition(), 1);

  /*
#ifdef MultiInterObjectsShadow
  coord = shadowMapCoord[iLightID];
#else 
  coord = shadowMapCoord;
#endif
*/

  coord = viewPos;

#ifdef CascadedShadow

  if(iIsCascaded == true)
  {
    cascadedOffset = NB_CASCADES-1;
    projMatrix  = g_shadowBuffer[iLightID + iCascadeID].ObjectViewToLightProjMatrix;
  }
  else
#endif
  {

#ifdef MultiShadow

    mat4 passageMatrix = _sbcbMultiShadow.PassageMatrix;

    projMatrix = g_shadowBuffer[iLightID+cascadedOffset].ObjectViewToLightProjMatrix * passageMatrix;
#else // Mono Shadow
    projMatrix = _sbcbShadow.ObjectViewToLightViewMatrix; 
#endif //MultiShadow

  }

#ifdef UsePassThroughTransition
  vec4 resCoord = offsetMatrix  * projMatrix * _vPrivGetViewInverseTransition() * coord;
#else
  vec4 resCoord = offsetMatrix  * projMatrix * coord;
#endif

  resCoord.xyz /= resCoord.w;

  //Clamp is made for globe
  resCoord.z = vClamp(resCoord.z, 0, 1);


  //We linearize the Z if we have a perspective projection
  if(IsPerspective(resCoord))
  {
    float n = GetLightNear(iLightID);
    float f = GetLightFar(iLightID);
    float z = resCoord.z;

    resCoord.z = ZClipRemmapedInLinear(n, f, z);
  }

  return resCoord;

}

int GetFaceIndex( int iLightID, vec3 iLightPos)
{
	vec3 pos = vGetViewPosition();
    vec3 lightDir = pos - iLightPos;

	lightDir = vec3(vGetViewInverseMatrix()  * vec4(lightDir, 0.0));
		
    int faceIndex;
    //find face index using lightdir
		   
    if(vAbs(lightDir.x) >= vAbs(lightDir.y) && vAbs(lightDir.x) >= vAbs(lightDir.z))
    {	   
       faceIndex = (lightDir.x > 0.0) ? 0  : 1;	 
    }
    else if(vAbs(lightDir.y) >= vAbs(lightDir.x) && vAbs(lightDir.y) >= vAbs(lightDir.z))
    {
       faceIndex = (lightDir.y > 0.0) ? 2  : 3;	
    }
    else
    {
       faceIndex = (lightDir.z > 0.0) ? 4  : 5;
    }
	   		   
    return iLightID + faceIndex;
   
 }


vec4 GetPointLightShadowCoord(int iLightID, vec3 iLightPos)
{
  mat4 offsetMatrix = mat4( 0.5, 0.0, 0.0, 0.0,
                            0.0, 0.5, 0.0, 0.0,
                            0.0, 0.0, 0.5, 0.0,
                            0.5, 0.5, 0.5, 1.0 );

  mat4 projMatrix;
  vec4 coord;
  
  vec4 viewPos = vec4(vGetViewPosition(), 1);
  coord = viewPos;

  int cascadedOffset = 0;
  #ifdef CascadedShadow
  cascadedOffset = NB_CASCADES-1; 
  #endif

  iLightID += cascadedOffset;

  int faceIndex = GetFaceIndex(iLightID,iLightPos);

 #ifdef MultiShadow
    mat4 passageMatrix = _sbcbMultiShadow.PassageMatrix;
    projMatrix = g_shadowBuffer[faceIndex].ObjectViewToLightProjMatrix * passageMatrix;
#endif //MultiShadow

#ifdef UsePassThroughTransition
  vec4 resCoord = offsetMatrix  * projMatrix * _vPrivGetViewInverseTransition() * coord;
#else
  vec4 resCoord = offsetMatrix  * projMatrix * coord;
#endif

  resCoord.xyz /= resCoord.w;

  //Clamp is made for globe
  resCoord.z = vClamp(resCoord.z, 0, 1);


  //We linearize the Z if we have a perspective projection
 if(IsPerspective(resCoord))
  {
    float n = GetLightNear(iLightID);
    float f = GetLightFar(iLightID);
    float z = resCoord.z;

    resCoord.z = ZClipRemmapedInLinear(n, f, z);
  }
  
  return resCoord;
  
}

int GetCascade()
{
#ifndef CascadedShadow
  return 0;
#else //In Cascaded

  int cascade = 0;
  
  float depth = -vGetViewPosition().z;

  vec4 cascadedPlane1 = _sbcbCascadedShadow.cascadePlanes[0];
  vec4 cascadedPlane2 = _sbcbCascadedShadow.cascadePlanes[1];

  #if NB_CASCADES > 7
    if(depth > cascadedPlane2.z)
      cascade = 7;
    else
  #endif

  #if NB_CASCADES > 6
    if(depth > cascadedPlane2.y)
      cascade = 6;
    else
  #endif


  #if NB_CASCADES > 5
    if(depth > cascadedPlane2.x)
      cascade = 5;
    else
  #endif

  #if NB_CASCADES > 4
    if(depth > cascadedPlane1.w)
      cascade = 4;
    else
  #endif

  #if NB_CASCADES > 3
    if(depth > cascadedPlane1.z)
      cascade = 3;
    else
  #endif
      
  #if NB_CASCADES > 2
    if(depth > cascadedPlane1.y)
      cascade = 2;
    else
  #endif

    if(depth > cascadedPlane1.x)
      cascade = 1;

  return cascade;
#endif //Cascaded
}


float fadeLastCascade(int iCascadeID)
{
#if defined(CascadedShadow) && LastCascadeFadingFactor > 0
  if(NB_CASCADES-1 == iCascadeID)
  {
    vec4 cascadedPlane1 = _sbcbCascadedShadow.cascadePlanes[0];
    vec4 cascadedPlane2 = _sbcbCascadedShadow.cascadePlanes[1];

    float depth = -vGetViewPosition().z;

    float nearPlane = 0;
    float farPlane  = cascadedPlane1.x;

  #if NB_CASCADES == 2
      nearPlane = cascadedPlane1.x;
      farPlane  = cascadedPlane1.y;
  #elif NB_CASCADES == 3
      nearPlane = cascadedPlane1.y;
      farPlane  = cascadedPlane1.z;
  #elif NB_CASCADES == 4
      nearPlane = cascadedPlane1.z;
      farPlane  = cascadedPlane1.w;
  #elif NB_CASCADES == 5
      nearPlane = cascadedPlane1.w;
      farPlane  = cascadedPlane2.x;
  #elif NB_CASCADES == 6
      nearPlane = cascadedPlane2.x;
      farPlane  = cascadedPlane2.y;
  #elif NB_CASCADES == 7
      nearPlane = cascadedPlane2.y;
      farPlane  = cascadedPlane2.z;
  #elif NB_CASCADES == 8
      nearPlane = cascadedPlane2.z;
      farPlane  = cascadedPlane2.w;
  #endif

      const float factor = float(LastCascadeFadingFactor) / 100.0;

      const float nearFarDistance = farPlane - nearPlane;

      const float startFadingZ = nearPlane + factor * nearFarDistance;
      const float startFadingZFarDistance = farPlane - startFadingZ;


       //The return is equivalent to the commented code below
      return vStep(startFadingZ, depth) * (depth - startFadingZ) / startFadingZFarDistance;
  
  /*  if(depth > startFadingZ)
        return (depth - startFadingZ) / startFadingZFarDistance
      else
        return 0.0;
  */
  }

#endif


  return 0.0;
}


int _GetLayer(int iLightID, int iCascadeID, bool isCascaded)
{
  int layer;

#ifndef CascadedShadow
  layer = iLightID;
#else //!CascadedShadow
  if(isCascaded)
    layer = iLightID + iCascadeID;
  else
  {
    if(iLightID < CascadedShadowLightID)
      layer = iLightID;
    else
      layer = NB_CASCADES + iLightID - 1; 
  }
#endif //CascadedShadow
  
  return layer;
}


#ifdef SHADOWS_USE_GPU_BIAS
float biasedZ(float z0, vec2 dz_duv, vec2 offset)
{
  return vClamp(z0 + dot(dz_duv, offset) - 0.005, 0.0, 1.0);
}
#elif defined(SHADOWS_USE_FIXED_BIAS)
float biasedZ(float z0, vec2 dz_duv, vec2 offset)
{
    return vClamp(z0  - 0.005, 0.0, 1.0);
}
#else //No bias
float biasedZ(float z0, vec2 dz_duv, vec2 offset)
{
    return z0;
}
#endif

float borderPCFTexture(sampler2DShadow tex, vec4 iPos)
{
  return vTexture(tex, iPos.xyz);
}

float borderPCFTexture(sampler2DArrayShadow tex, vec4 iPos)
{
  return vTexture(tex, iPos);
}

vec4 borderDepthTexture(sampler2D tex, vec4 iPos)
{
  return vTexture(tex, iPos.xy);
}

vec4 borderDepthTexture(sampler2DArray tex, vec4 iPos)
{
  return vTexture(tex, iPos.xyz);
}

vec4 sampleShadowMapTex2(in vec4 coord)
{
  #if defined(NonPointShadow) || defined(MultiNonPointShadows)
	#if defined(PCF) && defined(SO_EVISUPBR_SUBSURFACE) || defined(ESM) || defined(PCSS)
	 return borderDepthTexture(shadowMapTex2, coord);
	 #else
	 return vec4(0.0);
	#endif
  #else
   return vec4(0.0);
  #endif
}

  // Derivatives of light-space depth with respect to texture2D coordinates
vec2 depthGradient(vec2 uv, float z)
{
    vec2 dz_duv = vec2(0.0, 0.0);

    vec3 duvdist_dx = dFdx(vec3(uv,z));
    vec3 duvdist_dy = dFdy(vec3(uv,z));

    dz_duv.x = duvdist_dy.y * duvdist_dx.z;
    dz_duv.x -= duvdist_dx.y * duvdist_dy.z;

    dz_duv.y = duvdist_dx.x * duvdist_dy.z;
    dz_duv.y -= duvdist_dy.x * duvdist_dx.z;

    float det = (duvdist_dx.x * duvdist_dy.y) - (duvdist_dx.y * duvdist_dy.x);
    dz_duv /= det;

    return dz_duv;
}


//Define for PCF in case of PCSS
#if defined (PCSS_Filter_Poisson25)
  #define NBShadowPCFSample 25
  #define Poisson_PCF_Disk g_Poisson25
#elif defined (PCSS_Filter_Poisson32)
  #define NBShadowPCFSample 32
  #define Poisson_PCF_Disk g_Poisson32
#elif defined (PCSS_Filter_Poisson64)
  #define NBShadowPCFSample 64
  #define Poisson_PCF_Disk g_Poisson64
#elif defined (PCSS_Filter_Poisson100)
  #define NBShadowPCFSample 100
  #define Poisson_PCF_Disk g_Poisson100
#elif defined (PCSS_Filter_Poisson128)
  #define NBShadowPCFSample 128
  #define Poisson_PCF_Disk g_Poisson128
#endif //PCSS_Blocker


#ifndef Poisson_PCF_Disk
 #define Poisson_PCF_Disk g_Poisson32
#endif


  float PoissonPCF(bool iForPlane, vec2 iPoissonMultiplier, vec2 dz_duv,  int iLightID, const int iNbShadowSample, int iCascadeID, bool iIsCascaded)
  {
    const int nbShadowSample = iNbShadowSample;
  
    vec4 curShadowMapCoord = GetShadowMapCoord(iLightID, iCascadeID, iIsCascaded);
    
    float visibility = 0.;
  
    for(int i = 0 ; i < nbShadowSample ; ++i)
    {
      vec2 offset = Poisson_PCF_Disk[i] * iPoissonMultiplier;
      float biasedZ = biasedZ(curShadowMapCoord.z, dz_duv, offset);
    
      vec4 shadowMapCoordProj;
      shadowMapCoordProj.xy = curShadowMapCoord.xy;
      shadowMapCoordProj.z  = biasedZ;
      shadowMapCoordProj.w  = curShadowMapCoord.w;      
      
      shadowMapCoordProj.xy += offset;

      if(iForPlane == true && curShadowMapCoord.z >= 1)
      {
      #if defined(ShadowWorkWithArray) && defined(MultiNonPointShadows)
        shadowMapCoordProj.z = _GetLayer(iLightID, iCascadeID, iIsCascaded); //Layer
        shadowMapCoordProj.w = 0.9; //DRef
      #else //!ShadowWorkWithArray
        shadowMapCoordProj.z = 0.9; //Dref
      #endif
      }
      else
      {
      #if defined(ShadowWorkWithArray) && defined(MultiNonPointShadows)
        float tmp = shadowMapCoordProj.z;
        shadowMapCoordProj.z = _GetLayer(iLightID, iCascadeID, iIsCascaded); // layer
        shadowMapCoordProj.w = tmp; //Dref
      #endif //ShadowWorkWithArray
      }

	  #if defined(NonPointShadow) || defined(MultiNonPointShadows)
      visibility += borderPCFTexture(shadowMapTex, shadowMapCoordProj);
	  #endif
    }
  
 
    return visibility / float(nbShadowSample);
  }


  float SimplePCF(bool iForPlane, vec2 dz_duv, int iLightID, const int iNbShadowSample, int iCascadeID, bool iIsCascaded)
  {

    float minV = 0.;
    float maxV = 0.;
  
    const int nbShadowSample = iNbShadowSample;
  
    if(nbShadowSample == 4)
    {
      minV = -0.5; maxV = 0.5;
    }
    else if(nbShadowSample == 9)
    {
      minV = -1.;
      maxV = 1.;
    }
    else if(nbShadowSample == 16)
    {
      minV = -1.5;
      maxV = 1.5;
    }
    else if(nbShadowSample == 25)
    {
      minV = -2.;
      maxV = 2.;
    }
    else if(nbShadowSample == 36)
    {
      minV = -2.5;
      maxV = 2.5;
    }
    else if(nbShadowSample == 49)
    {
      minV = -3.;
      maxV = 3.;
    }
    else if(nbShadowSample == 64)
    {
      minV = -3.5;
      maxV = 3.5;
    }
    else if(nbShadowSample == 81)
    {
      minV = -4.;
      maxV = 4.;
    }
    else if(nbShadowSample == 100)
    {
      minV = -4.5;
      maxV = 4.5;
    }
    else if(nbShadowSample == 121)
    {
      minV = -5.;
      maxV = 5.;
    }
    else if(nbShadowSample == 144)
    {
      minV = -5.5;
      maxV = 5.5;
    }

  
    vec4 curShadowMapCoord = GetShadowMapCoord(iLightID, iCascadeID, iIsCascaded);


    float visibility = 0.;
  
    float tmpY = curShadowMapCoord.y;
  
    float i = 0.0;
    float j = 0.0;
  
    float pcfStep = 1./float(ShadowMapResolution);  
  
    for(i = minV ; i <= maxV ; i += 1.0)
    {

      for(j = minV ; j <= maxV ; j += 1.0)
      {	  
        vec4 shadowMapCoordProj = curShadowMapCoord;
      
        vec2 offset = vec2(i, j) * pcfStep;

        shadowMapCoordProj.xy += offset;
        shadowMapCoordProj.z   = biasedZ(curShadowMapCoord.z, dz_duv, offset);


        if(iForPlane == true && curShadowMapCoord.z >= 1.0)
        {
        #if defined(ShadowWorkWithArray) && defined(MultiNonPointShadows)
          shadowMapCoordProj.z = _GetLayer(iLightID, iCascadeID, iIsCascaded); //Layer
          shadowMapCoordProj.w = 0.9; //DRef
        #else //!ShadowWorkWithArray
          shadowMapCoordProj.z = 0.9; //Dref
        #endif
        }
        else
        {
        #if defined(ShadowWorkWithArray) && defined(MultiNonPointShadows)
          float tmp = shadowMapCoordProj.z;
          shadowMapCoordProj.z = _GetLayer(iLightID, iCascadeID, iIsCascaded); // layer
          shadowMapCoordProj.w = tmp; //Dref
        #endif //ShadowWorkWithArray
        }
			   
	    #if defined(NonPointShadow) || defined(MultiNonPointShadows)
        visibility += borderPCFTexture(shadowMapTex, shadowMapCoordProj);
		#endif 

      }
    }
  
    return visibility / float(nbShadowSample);
  }


  float sampleShadowMap(vec3 iShadowMapCoord, vec2 iOffset, float iInvShadowMap, vec2 dz_duv,
                        bool iForPlane, int iLightID, int iCascadeID, 
                        bool iIsCascaded)
  {
    iOffset *= iInvShadowMap;
    vec4 shadowMapCoordProj = vec4(iShadowMapCoord, 1.0);

    shadowMapCoordProj.xy += iOffset;
    shadowMapCoordProj.z   = biasedZ(iShadowMapCoord.z, dz_duv, iOffset);


    if(iForPlane == true && iShadowMapCoord.z >= 1.0)
    {
    #if defined(ShadowWorkWithArray) && defined(MultiNonPointShadows)
      shadowMapCoordProj.z = _GetLayer(iLightID, iCascadeID, iIsCascaded); //Layer
      shadowMapCoordProj.w = 0.9; //DRef
    #else //!ShadowWorkWithArray
      shadowMapCoordProj.z = 0.9; //Dref
    #endif
    }
    else
    {
    #if defined(ShadowWorkWithArray) && defined(MultiNonPointShadows)
      float tmp = shadowMapCoordProj.z;
      shadowMapCoordProj.z = _GetLayer(iLightID, iCascadeID, iIsCascaded); // layer
      shadowMapCoordProj.w = tmp; //Dref
    #endif //ShadowWorkWithArray
    }

    #if defined(NonPointShadow) || defined(MultiNonPointShadows)
    return borderPCFTexture(shadowMapTex, shadowMapCoordProj);
	#else
	return 0.0;
	#endif

	
  }

  float OptimizedPCF(bool iForPlane, vec2 dz_duv, int iLightID, const int iNbShadowSample, int iCascadeID, bool iIsCascaded)
  {

    float minV = 0.;
    float maxV = 0.;
  
    const int nbShadowSample = iNbShadowSample;
  
    vec4 curShadowMapCoord = GetShadowMapCoord(iLightID, iCascadeID, iIsCascaded);

  
    float tmpY = curShadowMapCoord.y;
  
    float i = 0.0;
    float j = 0.0;

    const float shadowMapSizeInv = 1./float(ShadowMapResolution);  

     vec2 uv = curShadowMapCoord.xy * ShadowMapResolution;

    vec2 base_uv;
    base_uv.x = floor(uv.x + 0.5);
    base_uv.y = floor(uv.y + 0.5);

    float s = (uv.x + 0.5 - base_uv.x);
    float t = (uv.y + 0.5 - base_uv.y);

    base_uv -= vec2(0.5, 0.5);
    base_uv *= shadowMapSizeInv;

    float sum = 0;

    vec3 shadowFetchPos = vec3(base_uv, curShadowMapCoord.z);

#if NBShadowPCFSample <= 4 //Filter size is 2

    return sampleShadowMap(shadowFetchPos, vec2(0, 0), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);

#elif NBShadowPCFSample <= 9  //Filter size is 3


    float uw0 = (3.0 - 2.0 * s);
    float uw1 = (1.0 + 2.0 * s);

    float u0 = (2.0 - s) / uw0 - 1.0;
    float u1 = s / uw1 + 1.0;

    float vw0 = (3.0 - 2.0 * t);
    float vw1 = (1.0 + 2.0 * t);

    float v0 = (2.0 - t) / vw0 - 1.0;
    float v1 = t / vw1 + 1.0;

    sum += uw0 * vw0 * sampleShadowMap(shadowFetchPos, vec2(u0, v0), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw1 * vw0 * sampleShadowMap(shadowFetchPos, vec2(u1, v0), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw0 * vw1 * sampleShadowMap(shadowFetchPos, vec2(u0, v1), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw1 * vw1 * sampleShadowMap(shadowFetchPos, vec2(u1, v1), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);

    return sum * 1.0f / 16.0;

#elif NBShadowPCFSample <= 25 //Filter size is 5

    float uw0 = (4 - 3 * s);
    float uw1 = 7;
    float uw2 = (1 + 3 * s);

    float u0 = (3 - 2 * s) / uw0 - 2;
    float u1 = (3 + s) / uw1;
    float u2 = s / uw2 + 2;

    float vw0 = (4 - 3 * t);
    float vw1 = 7;
    float vw2 = (1 + 3 * t);

    float v0 = (3 - 2 * t) / vw0 - 2;
    float v1 = (3 + t) / vw1;
    float v2 = t / vw2 + 2;

    sum += uw0 * vw0 * sampleShadowMap(shadowFetchPos, vec2(u0, v0), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw1 * vw0 * sampleShadowMap(shadowFetchPos, vec2(u1, v0), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw2 * vw0 * sampleShadowMap(shadowFetchPos, vec2(u2, v0), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw0 * vw1 * sampleShadowMap(shadowFetchPos, vec2(u0, v1), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw1 * vw1 * sampleShadowMap(shadowFetchPos, vec2(u1, v1), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw2 * vw1 * sampleShadowMap(shadowFetchPos, vec2(u2, v1), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw0 * vw2 * sampleShadowMap(shadowFetchPos, vec2(u0, v2), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw1 * vw2 * sampleShadowMap(shadowFetchPos, vec2(u1, v2), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw2 * vw2 * sampleShadowMap(shadowFetchPos, vec2(u2, v2), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);

    return sum * 1.0f / 144;


#else  //Filter size is 7
    float uw0 = (5 * s - 6);
    float uw1 = (11 * s - 28);
    float uw2 = -(11 * s + 17);
    float uw3 = -(5 * s + 1);

    float u0 = (4 * s - 5) / uw0 - 3;
    float u1 = (4 * s - 16) / uw1 - 1;
    float u2 = -(7 * s + 5) / uw2 + 1;
    float u3 = -s / uw3 + 3;

    float vw0 = (5 * t - 6);
    float vw1 = (11 * t - 28);
    float vw2 = -(11 * t + 17);
    float vw3 = -(5 * t + 1);

    float v0 = (4 * t - 5) / vw0 - 3;
    float v1 = (4 * t - 16) / vw1 - 1;
    float v2 = -(7 * t + 5) / vw2 + 1;
    float v3 = -t / vw3 + 3;

    sum += uw0 * vw0 * sampleShadowMap(shadowFetchPos, vec2(u0, v0), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw1 * vw0 * sampleShadowMap(shadowFetchPos, vec2(u1, v0), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw2 * vw0 * sampleShadowMap(shadowFetchPos, vec2(u2, v0), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw3 * vw0 * sampleShadowMap(shadowFetchPos, vec2(u3, v0), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw0 * vw1 * sampleShadowMap(shadowFetchPos, vec2(u0, v1), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw1 * vw1 * sampleShadowMap(shadowFetchPos, vec2(u1, v1), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw2 * vw1 * sampleShadowMap(shadowFetchPos, vec2(u2, v1), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw3 * vw1 * sampleShadowMap(shadowFetchPos, vec2(u3, v1), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw0 * vw2 * sampleShadowMap(shadowFetchPos, vec2(u0, v2), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw1 * vw2 * sampleShadowMap(shadowFetchPos, vec2(u1, v2), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw2 * vw2 * sampleShadowMap(shadowFetchPos, vec2(u2, v2), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw3 * vw2 * sampleShadowMap(shadowFetchPos, vec2(u3, v2), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw0 * vw3 * sampleShadowMap(shadowFetchPos, vec2(u0, v3), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw1 * vw3 * sampleShadowMap(shadowFetchPos, vec2(u1, v3), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw2 * vw3 * sampleShadowMap(shadowFetchPos, vec2(u2, v3), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);
    sum += uw3 * vw3 * sampleShadowMap(shadowFetchPos, vec2(u3, v3), shadowMapSizeInv, dz_duv, iForPlane, iLightID, iCascadeID, iIsCascaded);

    return sum * 1.0f / 2704;
#endif 

  }





  float SimplePCF(bool iForPlane, int iLightID, int iCascadeID, bool iIsCascaded)
  {
    vec4 uvz = GetShadowMapCoord(iLightID, iCascadeID, iIsCascaded);

    vec2 dzduv = depthGradient(uvz.xy, uvz.z);

    return SimplePCF(iForPlane, dzduv, iLightID, NBShadowPCFSample, iCascadeID, iIsCascaded);
  }

  float PoissonPCF(bool iForPlane, int iLightID, int iCascadeID, bool iIsCascaded)
  {
    vec4 uvz = GetShadowMapCoord(iLightID, iCascadeID, iIsCascaded);

    vec2 dzduv = depthGradient(uvz.xy, uvz.z);



    return PoissonPCF(iForPlane, vec2(2./ShadowMapResolution), dzduv, iLightID, NBShadowPCFSample, iCascadeID, iIsCascaded);
  }


  float OptimizedPCF(bool iForPlane, int iLightID, int iCascadeID, bool iIsCascaded)
  {
    vec4 uvz = GetShadowMapCoord(iLightID, iCascadeID, iIsCascaded);

    vec2 dzduv = depthGradient(uvz.xy, uvz.z);

    return OptimizedPCF(iForPlane, dzduv, iLightID, NBShadowPCFSample, iCascadeID, iIsCascaded);
  }





#ifdef ESM
  
#define V_POW3_DOUBLE(x) x * x * x 


float SimpleESM(bool iForPlane, int iLightID, int iCascadeID, bool iIsCascaded)
{    
 
  vec4 curShadowMapCoord = GetShadowMapCoord(iLightID, iCascadeID, iIsCascaded);
  
  const int layer = _GetLayer(iLightID, iCascadeID, iIsCascaded);
  float visibility = 0.;

 #if defined(NonPointShadow) || defined(MultiNonPointShadows)
	#ifndef CascadedShadow
	  vec4 expFromLight = borderDepthTexture(shadowMapESMTex, vec4(curShadowMapCoord.xy, layer, 0));
	#else
	  vec4 expFromLight = textureLod(shadowMapESMTex, vec3(curShadowMapCoord.xy, layer), 0);
	#endif

  float offsetZ = curShadowMapCoord.z - 0.001; 
  
  
  double a  = exp(-80.0 * offsetZ);
  a = V_POW3_DOUBLE(a);
  
  double bDouble = expFromLight.x;  
  double b = V_POW3_DOUBLE(bDouble);
  
   
  visibility = float(a * b);
   

  float invShadowMapRes = 1. / float(ShadowMapResolution);
  
  #ifdef ESM_THRESHOLD

    vec4 samplerPos = curShadowMapCoord;
	float myMax = 0;

	#ifndef ESM_PRECOMPUTEDMAP
    const int detectionKernel = 4;

    for(int i = -detectionKernel ; i <= detectionKernel ; ++i)
    for(int j = -detectionKernel ; j <= detectionKernel ; ++j)
    {
       vec4 samplingPos = vec4(samplerPos.xy + vec2(i * invShadowMapRes, j * invShadowMapRes), layer, 0);

       myMax = max(myMax, borderDepthTexture(shadowMapTex2, samplingPos).x);
    }
	#else
	  myMax = borderDepthTexture(shadowMapMaxZTex, samplerPos).x;
	#endif //ESM_PRECOMPUTEDMAP

      if( curShadowMapCoord.z + 0.03 < myMax)
      {

         //discard; //uncomment this to see the threshold (debug feature)

        visibility = SimplePCF(false, iLightID, iCascadeID, iIsCascaded);
      }

   #endif //ESM_THRESHOLD
   #endif

   
  return clamp(visibility, 0, 1);
}


#endif //ESM



float zClipToEye(float z, int iLayer)
{
  float lightNear = GetLightNear(iLayer);
  float lightFar  = GetLightFar(iLayer);

  return ZClipToEye(lightNear, lightFar, z);

  //return (2 * lightNear * lightFar) / (lightFar + lightNear - z * (lightFar - lightNear));

  //return lightFar * lightNear / (lightFar - z * (lightFar - lightNear));   
}

#ifdef PCSS

  ////// PCSS Defines ////////

  //Define for search blocker parts
#if defined (PCSS_Blocker_Poisson25)
  #define PCSS_Blocker_Poisson_NbSample 25
  #define PCSS_Blocker_Poisson_Disk g_Poisson25
#elif defined (PCSS_Blocker_Poisson32)
  #define PCSS_Blocker_Poisson_NbSample 32
  #define PCSS_Blocker_Poisson_Disk g_Poisson32
#elif defined (PCSS_Blocker_Poisson64)
  #define PCSS_Blocker_Poisson_NbSample 64
  #define PCSS_Blocker_Poisson_Disk g_Poisson64
#elif defined (PCSS_Blocker_Poisson100)
  #define PCSS_Blocker_Poisson_NbSample 100
  #define PCSS_Blocker_Poisson_Disk g_Poisson100
#elif defined (PCSS_Blocker_Poisson128)
  #define PCSS_Blocker_Poisson_NbSample 128
  #define PCSS_Blocker_Poisson_Disk g_Poisson128
#else
  ERROR : No Poisson blocker is defined !;
#endif //PCSS_Blocker

vec2 GetLightDirRadius(int iLayer)
{
#ifndef MultiShadow
  return vec2(_sbcbShadow.LightNearFarDirRadius.z);
#else
  return vec2(g_shadowBuffer[iLayer].LightNearFarDirRadius.z);
#endif
}


vec2 searchRegionRadiusUV(float zWorld, int iLayer)
{
  float lightNear     =  GetLightNear(iLayer);

  vec2  lightRadiusUV = GetLightDirRadius(iLayer);

  return lightRadiusUV * (zWorld - lightNear) / zWorld;
}


// Using similar triangles between the area light, the blocking plane and the surface point
vec2 penumbraRadiusUV(float zReceiver, float zBlocker, int iLayer)
{

    vec2  lightRadiusUV = GetLightDirRadius(iLayer);

    return lightRadiusUV * (zReceiver - zBlocker) / zBlocker;
}



// Project UV size to the near plane of the light
vec2 projectToLightUV(vec2 sizeUV, float zWorld, int iLayer)
{
    float lightNear = GetLightNear(iLayer);

    return sizeUV * lightNear / zWorld;
}



  void findBlocker( out float accumBlockerDepth, 
                    out int   numBlockers,
                    vec2 uv,
                    float z0,
                    vec2 dz_duv,
                    vec2 searchRegionRadiusUV,
                    int iLayer
                    )
  {
    accumBlockerDepth = 0.0;
    numBlockers = 0;

    for (int i = 0; i < PCSS_Blocker_Poisson_NbSample; ++i)
    {
      vec2 offset = PCSS_Blocker_Poisson_Disk[i] * searchRegionRadiusUV;

      vec4 samplingPos;

      samplingPos.xy = uv + offset;
      samplingPos.z  = iLayer;

	  float shadowMapDepth = 0.0;
	  #if defined(NonPointShadow) || defined(MultiNonPointShadows)
        shadowMapDepth = borderDepthTexture(shadowMapTex2, samplingPos).x;
	  #endif
      float z = biasedZ(z0, dz_duv, offset);
      if (shadowMapDepth < z)
      {
        accumBlockerDepth += shadowMapDepth;
        numBlockers++;
      }
    }
  }

    float SimplePCSS(bool iForPlane, int iLayer, int iCascadeID, bool iIsCascaded)
    {
      vec4 curShadowMapCoord = GetShadowMapCoord(iLayer, iCascadeID, iIsCascaded);

      vec2 uv = curShadowMapCoord.xy;
      float z = curShadowMapCoord.z;

      vec2 dz_duv = depthGradient(uv, z);
      float zEye = zClipToEye(z, iLayer);
      
      // ------------------------
      // STEP 1: blocker search
      // ------------------------
      float accumBlockerDepth;
      int numBlockers;
      vec2 searchRegionRadiusUV = searchRegionRadiusUV(zEye, iLayer);

      findBlocker(accumBlockerDepth, numBlockers, uv, z, dz_duv, searchRegionRadiusUV, iLayer);

#ifdef PCSS_DEBUG_OCCLUDER
      return vClamp(1 - numBlockers / 10.0f, 0, 1);
#endif

      // Early out if not in the penumbra
      if (numBlockers == 0)
          return 1.0;

      // ------------------------
      // STEP 2: penumbra size
      // ------------------------
      float avgBlockerDepth = accumBlockerDepth / numBlockers;
      float avgBlockerDepthWorld = zClipToEye(avgBlockerDepth, iLayer);
      vec2 penumbraRadius = penumbraRadiusUV(zEye, avgBlockerDepthWorld, iLayer);
      vec2 filterRadius = projectToLightUV(penumbraRadius, zEye, iLayer);

      
      // ------------------------
      // STEP 3: filtering
      // ------------------------

      return PoissonPCF(iForPlane, filterRadius, dz_duv, iLayer, NBShadowPCFSample, iCascadeID, iIsCascaded);


     // return pcfFilter(uv, z, dz_duv, filterRadius);
    }


#endif //PCSS








float _ComputeShadowsAlgo(bool iForPlane, int iLightID)
{
  float visibility = 0.;

  bool isCascaded = false;

  int cascadeID = 0;

#ifdef CascadedShadow

  isCascaded = (iLightID == CascadedShadowLightID);

  if(isCascaded)
  {
    cascadeID = GetCascade();
  }
#endif

  
  #ifdef PCF
  
    #ifdef PoissonSampling
      visibility = PoissonPCF(iForPlane, iLightID, cascadeID, isCascaded);
    #elif defined OptimizedSampling
      visibility = OptimizedPCF(iForPlane, iLightID, cascadeID, isCascaded);
    #else //basic PCF
      visibility = SimplePCF(iForPlane, iLightID, cascadeID, isCascaded);
    #endif

    visibility *= 1.3;

  #elif defined(ESM) 
  
    visibility = SimpleESM(iForPlane, iLightID, cascadeID, isCascaded);
    
  #elif defined(PCSS)

    visibility = SimplePCSS(iForPlane, iLightID, cascadeID, isCascaded);

    visibility *= 1.3;


  #endif //PCSS
  
  if(isCascaded == true)
    visibility += fadeLastCascade(cascadeID);

  visibility = vClamp(visibility, 0., 1.);

  return visibility;
}

 vec4 SampleCubemapTex(samplerCube cubeMapTex, vec3 ilightDir, int iLightLayer)
 {
    return vTexture(cubeMapTex, vNormalize(ilightDir)); 
 }

 vec4 SampleCubemapTex(samplerCubeArray cubeMapTex, vec3 ilightDir, int iLightLayer)
 {
    return vTexture(cubeMapTex, vec4(vNormalize(ilightDir), iLightLayer)); 
 }

 vec3 GetLightDir(vec3 iLightPos)
 {
    vec3 pos = vGetViewPosition();
    vec3 lightDir = pos - iLightPos;
	return vec3(vGetViewInverseMatrix()  * vec4(lightDir, 0.0));
 }
 
 float SimplePCFPointLight(bool iForPlane, int iLightID, const int iNbShadowSample, int iLightLayer, vec3 iLightPos)
 {

    float offsetValue = 0.;
 
    const int nbShadowSample = iNbShadowSample;
  
    if(nbShadowSample == 4)
    {
      offsetValue = 0.2;
    }
    else if(nbShadowSample == 9)
    {
      offsetValue = 0.5;
    }
    else if(nbShadowSample == 36)
    {
      offsetValue = 1.3;
    }
    else if(nbShadowSample == 144)
    {
      offsetValue = 2.9;
    }
	  
    vec4 curShadowMapCoord = GetPointLightShadowCoord(iLightID, iLightPos);
	
    float visibility = 0.;
    
    float i = 0.0;
    float j = 0.0;

	vec3 lightDir = GetLightDir(iLightPos);
	float invShadowMapRes = 1. / float(ShadowMapResolution);
  
    for(i = -offsetValue ; i <= offsetValue ; i += 0.5)
    {
      for(j = -offsetValue ; j <= offsetValue ; j += 0.5)
      {	  
        vec3 offset = vec3(i, j , 0);
		float sampledDistance = 0.;
		float currentDistance  = curShadowMapCoord.z;
 
        if(iForPlane == true && curShadowMapCoord.z >= 1.0)
         currentDistance = 0.9; //DRef
        
		#if defined(PointLightShadows) || defined(MultiPointLightShadows)
		sampledDistance = SampleCubemapTex(ShadowCubeMapTex,(lightDir + offset),iLightLayer).r;
		#endif

		visibility += (currentDistance > sampledDistance) ? 0.0 : 1.0;
      }
    }
  
    return visibility / float(nbShadowSample);
  }

#ifdef ESM

float SimpleESMPointLight(bool iForPlane, int iLightID, int iLightLayer, vec3 iLightPos)
{    

  vec4 curShadowMapCoord = GetPointLightShadowCoord(iLightID, iLightPos);
  vec3 lightDir = GetLightDir(iLightPos);
  float visibility = 0.;

  #if defined(PointLightShadows) || defined(MultiPointLightShadows)
  vec4 expFromLight = SampleCubemapTex(ShadowCubeMapESMTex, lightDir, iLightLayer);
  
  float offsetZ = curShadowMapCoord.z - 0.001; 
    
  double a  = exp(-80.0 * offsetZ);
  a = V_POW3_DOUBLE(a);
  
  double bDouble = expFromLight.x;  
  double b = V_POW3_DOUBLE(bDouble);
     
  visibility = float(a * b);
     
  #ifdef ESM_THRESHOLD

	float myMax = 0;
	const float detectionKernel = 1.3;
	
    for(float i = -detectionKernel ; i <= detectionKernel ; i += 0.5)
    for(float j = -detectionKernel ; j <= detectionKernel ; j += 0.5)
    {
      vec3 offset = vec3(i, j , 0);
	  float sampledDistance = 0;
	  #if defined(PointLightShadows) || defined(MultiPointLightShadows)
	   sampledDistance = SampleCubemapTex(ShadowCubeMapTex,(lightDir + offset),iLightLayer).r;
	  #endif
      myMax = max(myMax, sampledDistance);
    }
	
    if( curShadowMapCoord.z + 0.03 < myMax)
    {
      //discard; //uncomment this to see the threshold (debug feature)

       visibility = SimplePCFPointLight(iForPlane, iLightID, 9, iLightLayer, iLightPos); // NBShadowPCFSample 9
    }

   #endif //ESM_THRESHOLD
   #endif
   
  return clamp(visibility, 0, 1);
}
#endif //ESM

float _ComputePointLightShadowsAlgo(bool iForPlane, int iLightID, int iLightLayer, vec3 iLightPos)
{
  float visibility = 0.;
  
  #ifdef ESM
  visibility = SimpleESMPointLight(iForPlane, iLightID, iLightLayer, iLightPos); 
  #else
  visibility = SimplePCFPointLight(iForPlane, iLightID, NBShadowPCFSample, iLightLayer, iLightPos); 
  visibility *= 1.3;
  #endif

  visibility = vClamp(visibility, 0., 1.);
  return visibility;
}

#if defined(MultiShadow) || defined(MultiInterObjectsShadow)
float ComputePointLightShadows(int iLightID, int iLightLayer, vec3 iLightPos)
{
  return _ComputePointLightShadowsAlgo(false, iLightID, iLightLayer, iLightPos);
}
#else 
float ComputePointLightShadows(vec3 iLightPos)
{
  return _ComputePointLightShadowsAlgo(false, 0, 0, iLightPos);
}
#endif

#if defined(MultiShadow) || defined(MultiInterObjectsShadow)
float ComputeShadows(int iLightID)
{
  return _ComputeShadowsAlgo(false, iLightID);
}
#else 
float ComputeShadows()
{
  return _ComputeShadowsAlgo(false, 0);
}
#endif

vec3 DebugCascade(float iFragDepth)
{
  vec3 debugColor = vec3(1,0,0);
  
  int curCascade = GetCascade();
  
  if( curCascade == 1)
    debugColor = vec3(0,1,0);
    
  if(curCascade == 2)
    debugColor = vec3(0,0,1);
    
  if(curCascade == 3)
    debugColor = vec3(1,1,0);

  if(curCascade == 4)
    debugColor = vec3(1,0,1);

  if(curCascade == 5)
    debugColor = vec3(0,1,1);

  if(curCascade == 6)
    debugColor = vec3(1,0.39,0); //Orange

  if(curCascade == 7)
    debugColor = vec3(0.34,0.16,0); //Marron


  return debugColor;
}

//Transparent Shadows map
vec4 GetTransparentShadowMap(int iShadowLayer)
{
	#if defined(TransparentShadow) && (defined(NonPointShadow) || defined(MultiNonPointShadows))
		vec4 transShadowCoordinates = GetShadowMapCoord(iShadowLayer, 0, false);
			
		int cascadeID = 0;
		bool isCascaded = false;
		#ifdef CascadedShadow
		  isCascaded = (iShadowLayer == CascadedShadowLightID);
		  if(isCascaded)
		  {
			cascadeID = GetCascade();
		  }
		#endif //CascadedShadow
		
		const int layer = _GetLayer(iShadowLayer, cascadeID, isCascaded);
		vec4 transShadowFrag = borderDepthTexture(transparentShadowMapTex, vec4(transShadowCoordinates.xy, layer, 0));
		return transShadowFrag;
	#endif //TransparentShadow 
	return vec4(1.);
}
#endif //ShadowMap

